// Copyright(c) 2008 Tri Tech Information Systems Inc. 
// Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file ../../LICENSE_1_0.txt or copy at
//           http://www.boost.org/LICENSE_1_0.txt)
//     
#ifndef INCLUDE_PYTHON_ARG_H
#define INCLUDE_PYTHON_ARG_H

#include <boost/python.hpp>
#include <vector>
#include <boost/mpl/if.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/type_traits/is_pointer.hpp>
#include <iostream>


/*
    Boost::Python has an arg class which nicely handles adding keyword arguments. It also allows default values in those keyword arguments, but...

    The arguments are converted into python at the time of defining the interface. This means that related enums and classes have to be defined first.
    This goes against most of boost::python in which you can do it in any order.

    defarg is a template function that given any c++ value, makes a copy of it and returns another value which can be converted by boost::python into a python object.
    When boost::python attempts to convert the value back into c++,
    it is converted into the value held.

    Essentially, it makes default arguments work but never actually converting
    the values into python.


   */
namespace defarg_detail
{
    using namespace boost::python;
    using namespace boost;

    /*
       This is the base class right now it does nothing
        TODO: record C++ default value expression text for use in repr
    */   
    struct defarg_base
    {
        const char * default_value;

        defarg_base(const char * value):
            default_value(value)
        {
        }
            
    };

    /*
       This class is used if the default value is a pointer.
       The only reason I am aware of for a default value of pointer is NULL
    */   
    template< typename PtrType >
    struct defarg_pointer : public defarg_base
    {
        typedef typename boost::remove_pointer<PtrType>::type ValueType;
        defarg_pointer(const char * string_value, PtrType value) :
            defarg_base(string_value),
            m_ptr(value)
        {
            if(!exposed)
            {
                converter::registry::push_back(
                    &defarg_pointer<PtrType>::convertible,
                    &defarg_pointer<PtrType>::construct,
                    type_id<ValueType>() );
                exposed = true;
            }

        }

        PtrType m_ptr;
 
        static boost::python::object execute(const char * string_value, PtrType value)
        {
            if(value == 0)
            {
                // In this case we actually give python a None
                // Interestingly, its not possible to implement a converter
                // that produce NULL, since that tells python to find
                // another convertor
                return boost::python::object();
            }
            else
            {
                // Rarely its useful to have a pointer passed through this
                using namespace boost::python;
                return object( ptr( (defarg_base*)new defarg_pointer<PtrType>(string_value, value)) );
            }
        }           

        static void * convertible( PyObject * obj )
        {
            extract<defarg_base*> extractor(obj);
            if(extractor.check())
                return obj;
            else
                return 0;
        }
    
        static void construct(PyObject * obj, converter::rvalue_from_python_stage1_data * data)
        {
            /*
               If this is being called C++ wants either a const reference
               or a value.

               If its a value, it'll be copied away
               If its a const reference, its not allowed to modify it
               or make any assumptions about its lifetime.
               So we can just give a pointer to what we are holding
            */
            defarg_base * base = extract<defarg_base*>(obj);
            defarg_pointer<PtrType> * actual = reinterpret_cast< defarg_pointer<PtrType> *>(base);
            data->convertible = (void*)actual->m_ptr;
        }        

        static bool exposed;
    };    
    template< typename PtrType>
    bool defarg_pointer<PtrType>::exposed = false;




    template< typename ValueType>
    struct defarg_value : public defarg_base
    {
        defarg_value(const char * string_value, ValueType value) :
            defarg_base(string_value),
            m_value(value)
        {
            if(!exposed)
            {
                converter::registry::push_back(
                    &defarg_value<ValueType>::convertible,
                    &defarg_value<ValueType>::construct,
                    type_id<ValueType>() );
                exposed = true;
            }
        }

        static boost::python::object execute(const char * string_value, ValueType value)
        {
            using namespace boost::python;
            return object( ptr( (defarg_base*)new defarg_value<ValueType>(string_value, value)) );
        }

        static void * convertible( PyObject * obj )
        {
            extract<defarg_base*> extractor(obj);
            if(extractor.check())
                return obj;
            else
                return 0;
        }
    
        static void construct(PyObject * obj, converter::rvalue_from_python_stage1_data * data)
        {
            /*
               If this is being called C++ wants either a const reference
               or a value.

               If its a value, it'll be copied away
               If its a const reference, its not allowed to modify it
               or make any assumptions about its lifetime.
               So we can just give a pointer to what we are holding
            */
            defarg_base * base = extract<defarg_base*>(obj);
            defarg_value<ValueType> * actual = reinterpret_cast< defarg_value<ValueType> *>(base);
            data->convertible = (void*)&actual->m_value;
        }        

        ValueType m_value;
        static bool exposed;
    };    
    template< typename ValueType>
    bool defarg_value<ValueType>::exposed = false;

}


template< typename ValueType >
boost::python::object defarg(const char * string_value, ValueType value)
{
    using namespace boost::python;
    using namespace boost::mpl;
    using namespace boost;

    typedef typename if_<
        is_pointer<ValueType>,
        defarg_detail::defarg_pointer< ValueType >,
        typename if_<
            is_reference<ValueType>,
            typename defarg_detail::defarg_value< typename remove_reference<ValueType>::type >,
            typename defarg_detail::defarg_value< ValueType > 
        >::type 
    >::type type;
    return type::execute(string_value, value);
}




#endif
